﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using alib.Debugging;

using alib;
using alib.Collections.ReadOnly;
using alib.Dictionary;

using agree.schema;
using agree.Pmrs;
using agree.semi;
using agree.mrs;

namespace agree.semi
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class DerivationToMrs : m_scalar_base<IDerivation, Mrs>, ρρVpm
	{
		public DerivationToMrs(IRtParent parent, IDerivation item)
			: base(parent, item)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		Vpm vpm;
		public Vpm Vpm
		{
			get { return vpm; }
			set { this.vpm = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		protected override Mrs start(IDerivation deriv)
		{
			return deriv.DerivationTfs().Mrs(vpm);
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class MrsImporter : m_scalar_base<Mrs, Tfs>, ρρGrammar
	{
		public MrsImporter(IRtParent parent, Mrs item)
			: base(parent, item)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		Grammar g;
		public Grammar Grammar
		{
			get { return g; }
			set { this.g = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		protected PortableMrs pmrs;
		protected MrsManager mrsm;
		protected Dictionary<complex, List<int>> node_features;
		Dictionary<Constraint, variable> var_dict;

		protected override Tfs start(Mrs mrs)
		{
			this.mrsm = g.mrsm;

			this.node_features = new Dictionary<complex, List<int>>();

			this.var_dict = new Dictionary<Constraint, variable>();

			this.pmrs = new PortableMrs(this, mrsm);

			make_pmrs_helper(mrs);

			var tfs = pmrs.Write(g.fsu, g.ftm.r_none, new Dictionary<String, String>(), Tfs.Flags.Skolemized | Tfs.Flags.WantVarbits);

			tfs.flags |= Tfs.Flags.FromVpm;

			return tfs;
		}

		void make_pmrs_helper(Mrs mrs)
		{
			foreach (var v in mrs.Variables)
				var_dict[v] = MakeVar(v);

			var p_hook = new hook(pmrs, "hook");
			var mh = mrs.HOOK;
			p_hook.SetFeatureValue(mrsm.LTOP, GetVarOrMakeVacuous(mh.LTOP, mrsm.LTOP));
			p_hook.SetFeatureValue(mrsm.INDEX, GetVarOrMakeVacuous(mh.INDEX, mrsm.INDEX));

			if (mh.XARG != null)
				p_hook.SetFeatureValue(mrsm.XARG, GetVarOrMakeVacuous(mh.XARG, mrsm.XARG));

			pmrs.hook = p_hook;

			foreach (var rel in mrs.RELS)
				pmrs.rels.Add(MakeRelation(rel));

			foreach (var qeq in mrs.HCONS)
				pmrs.hcons.Add(MakeQeq(qeq));

			pmrs.EquateQeqScopes();

			FixSubstructureNodeTypes();
		}

		protected abstract variable MakeVar(Constraint v);

		variable GetVarOrMakeVacuous(Variable v, String arg)
		{
			return v != null && var_dict.ContainsKey(v) ? var_dict[v] : GetNewVarByFeat(arg);
		}

		variable GetNewVarByFeat(String feat_name)
		{
			if (mrsm.IsHandleFeature(feat_name))
				return new Pmrs.H(pmrs, mrsm.handle_type.Name);

			return new Pmrs.U(pmrs, mrsm.event_or_index_type.Name);
		}

		relation MakeRelation(Relation rel)
		{
			var p_rel = new relation(pmrs, "relation");

			if (rel.PRED != null)
				p_rel.SetFeatureValue(mrsm.PRED, new atomic_string(pmrs, rel.PRED));

			if (rel.CARG != null)
				p_rel.SetFeatureValue(mrsm.CARG, new atomic_string(pmrs, rel.CARG));

			foreach (var role in rel)
			{
				variable v = GetVarOrMakeVacuous(role.var, role.name);

				p_rel.SetFeatureValue(role.name, v);
			}
			return p_rel;
		}

		qeq MakeQeq(Qeq q)
		{
			var p_qeq = new qeq(pmrs, "qeq");

			Pmrs.H harg, larg;

			if (var_dict.TryGetValue(q.HARG, out harg) && harg is Pmrs.H)
				p_qeq.SetFeatureValue(mrsm.HARG, harg);

			if (var_dict.TryGetValue(q.LARG, out larg) && larg is Pmrs.H)
				p_qeq.SetFeatureValue(mrsm.LARG, larg);

			return p_qeq;
		}

		void FixSubstructureNodeTypes()
		{
			foreach (var cplx in node_features.Keys.Where(c => !(c is variable)))
			{
				var ftm = mrsm.ftm;
				var node_type = mrsm.im.GetMaximalTypeForFeatures(node_features[cplx]);
				cplx.label_type = new atomic_type(pmrs, node_type.Name);
			}
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class VpmReverse : MrsImporter, ρρVpm
	{
		public VpmReverse(IRtParent parent, Mrs item)
			: base(parent, item)
		{
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		Vpm vpm;
		public Vpm Vpm
		{
			get { return vpm; }
			set { vpm = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		protected override Tfs start(Mrs mrs)
		{
			var tfs = base.start(mrs);

			tfs.flags |= Tfs.Flags.FromVpm;

			return tfs;
		}

		protected override variable MakeVar(Constraint v)
		{
			VpmRule type_rule = FindApplicableRule(Vpm.VariableMap, v);
			variable v_out;

			String left_type = type_rule.left[0].TypeName;
			if (v.Type == "h")//(v is mrs.type.H)
				v_out = new Pmrs.H(pmrs, left_type);
			else if (v.Type == "e")//(v is mrs.type.E)
				v_out = new Pmrs.E(pmrs, left_type);
			else if (v.Type == "x")//(v is mrs.type.X)
				v_out = new Pmrs.X(pmrs, left_type);
			else
				v_out = new Pmrs.U(pmrs, left_type);

			foreach (var sec in Vpm.Sections)
			{
				var rule = FindApplicableRule(sec, v);
				if (rule != null)
				{
					for (int i = 0; i < sec.Header.LeftProps.Length; i++)
					{
						if (rule.left[i].Kind != VpmArgumentKind.Type)
							continue;

						var prop_path = sec.Header.LeftProps[i];

						//I have assumed that two sections shouldn't be setting the same properties...
						AddPropertyToVar(v_out, prop_path, rule.left[i].TypeName);//.Name);
					}
				}
			}
			return v_out;
		}

		VpmRule FindApplicableRule(VpmSection sec, Constraint var)
		{
			String[] props = new String[sec.Header.RightProps.Length];
			bool[] failed_to_find = new bool[sec.Header.RightProps.Length];
			for (int i = 0; i < props.Length; i++)
			{
				if (sec.Header.RightProps[i] == "")
				{
					props[i] = var.Type;//.Name;
				}
				else
				{
					String prop = var[sec.Header.RightProps[i]];
					if (prop != null)
						props[i] = prop;
				}
			}

			foreach (var rule in sec)
			{
				if (rule.op == VpmOperator.Forward)
					continue;

				for (int i = 0; i < props.Length; i++)
				{
					VpmArgumentKind kind = rule.right[i].Kind;

					if (failed_to_find[i] && kind != VpmArgumentKind.DefaultForType)
						goto next_rule;

					switch (kind)
					{
						case VpmArgumentKind.Type:
							if (rule.right[i].TypeName != props[i])
								goto next_rule;
							break;
						case VpmArgumentKind.DefaultForType:
							if (rule.right[i].TypeName != var.Type)
								goto next_rule;
							break;
						case VpmArgumentKind.Omit:
							if (!failed_to_find[i])
								goto next_rule;
							break;
						default: //(this should be only VpmArgumentKind.Default as Omit should never happen here)
							break;
					}
				}
				return rule;
			next_rule: ;
			}
			return null;
		}

		void AddPropertyToVar(variable v, BootstrapFsPath path, String value)
		{
			complex cur_node = v;
			node_features[v] = new List<int>();

			for (int i = 0; i < path.Count; i++)
			{
				var feat_name = path[i];
				node_features[cur_node].Add(Grammar.ftm[feat_name].i_feat);

				if (i < path.Count - 1)
				{
					if (!cur_node.rgav.ContainsKey(feat_name))
					{
						var cplx = new complex(pmrs, new atomic_type(pmrs, ""));
						cur_node.SetFeatureValue(feat_name, cplx);
						node_features[cplx] = new List<int>();
					}

					cur_node = cur_node.rgav[feat_name] as complex;

					if (cur_node == null)
						throw new Exception("incompatible feature paths found.");
				}
				else
				{
					cur_node.SetFeatureValue(feat_name, new atomic_type(pmrs, value));
				}
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class vpm_executor_base : IIdentity, ρρVpm
	{
		public vpm_executor_base(IIdentity prv)
		{
			this.prv = prv;
		}

		///////////////////////////////////////////////////////////////////////
		/// 
		protected Vpm vpm;
		public Vpm Vpm
		{
			get { return vpm; }
			set { vpm = value; }
		}
		/// 
		///////////////////////////////////////////////////////////////////////

		IIdentity prv;
		public IIdentity Trace { get { return prv; } }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class VpmForward : vpm_executor_base
	{
		public VpmForward(IIdentity prv)
			: base(prv)
		{
		}

		Mrs mrs;
		MrsManager mrsm;
		SemI semi;
		SlotDictionary<int, Variable> vars;

		int absent_mark_seq;

		public Mrs build(TfsSlot ts_mrs)
		{
			this.mrsm = ts_mrs.tfs.mrsm;
			this.semi = mrsm.SemI;
			if (semi == null)
				return null;
			this.vars = new SlotDictionary<int, Variable>(19);
			this.absent_mark_seq = int.MaxValue;

			mrs = new Mrs(this);
			start(ts_mrs);


			//Debug.Print("{0} {1}", ts_mrs.Tfs.c_corefs, mrs.Variables.Length);
			//if (ts_mrs.Tfs.c_corefs < mrs.Variables.Length)
			//	throw new Exception();

			return mrs;
		}

		void start(TfsSlot ts_mrs)
		{
			Relation rel;

			foreach (var ts_rel in mrsm.GetRelSlots(ts_mrs))
				if ((rel = MakeRel(ts_rel)) != null)
					mrs.RELS.Add(rel);

			foreach (var ts_qeq in mrsm.GetQeqSlots(ts_mrs))
				mrs.HCONS.Add(MakeQeq(ts_qeq));

			var h = mrs.HOOK;
			h.LTOP = GetOrMakeVar(mrsm.GetLtopSlot(ts_mrs)) as Handle;
			h.XARG = GetOrMakeVar(mrsm.GetXargSlot(ts_mrs));
			h.INDEX = GetOrMakeVar(mrsm.GetIndexSlot(ts_mrs));
		}

		Relation MakeRel(TfsSlot ts)
		{
			String pred = ts[mrsm.ifeat_pred].TypeNameOrStringValue;

			AppropArgsList aal;
			if (!semi.PredicateArgs.TryGetValue(pred, out aal))
			{
				Debug.Print("MRS builder is skipping rel for non-SemI pred '{0}'", pred);
				return null;
			}

#if false
			pred = pred.RemoveSuffix(mrsm.RelationSuffix, StringComparison.InvariantCultureIgnoreCase);
#endif

			Relation rel = new Relation(mrs, pred);

			var ef_carg = ts[mrsm.ifeat_carg].EdgeFlag;
			if (ef_carg >= 0)
				rel.CARG = mrsm.tu.TypeNameOrStringValue(ef_carg);

			var use_args = ts.SlotsBelow.Where(x => x.i_feat == mrsm.ifeat_lbl || aal.ContainsFeature(x.FEATURE));

			ProcessMrsElementVars(rel, use_args);

			return rel;
		}

		Qeq MakeQeq(TfsSlot ts)
		{
			var qeq = new Qeq(mrs);

			ProcessMrsElementVars(qeq, ts.SlotsBelow.Where(x => x.i_feat == mrsm.ifeat_larg || x.i_feat == mrsm.ifeat_harg));

			return qeq;
		}

		void ProcessMrsElementVars(MrsElement el, IEnumerable<TfsSlot> slots)
		{
			el.ToString();
			foreach (var ts in slots)
				el[ts.FEATURE] = GetOrMakeVar(ts);
		}

		Variable GetOrMakeVar(TfsSlot ts)
		{
			Variable v;
			int m;

			if (!ts.IsValid)
			{
				m = absent_mark_seq--;
				v = new Variable(mrs, "u", vars.Count);
			}
			else if (vars.TryGetValue(m = ts.out_mark, out v))
				return v;
			else
				v = MakeVar(ts);

			vars.Add(m, v);
			mrs.AddVariable(v);
			return v;
		}

		Variable MakeVar(TfsSlot ts)
		{
			var type_rule = FindApplicableRule(vpm.VariableMap, ts);
			Variable v;
			String right_type;
			int num = vars.Count;
			if (type_rule == null)
			{
				Debug.Print("no VPM mapping for '{0}'; using 'u'", ts.TypeNameOrStringValue);
				v = new Variable(mrs, "u", num);
			}
			else if ((right_type = type_rule.right[0].TypeName) == "h")
				v = new Handle(mrs, num);
			else
				v = new Variable(mrs, right_type, num);

			foreach (var sec in vpm.Sections)
			{
				var rule = FindApplicableRule(sec, ts);
				if (rule != null)
				{
					for (int i = 0; i < sec.Header.RightProps.Length; i++)
					{
						if (rule.right[i].Kind != VpmArgumentKind.Type)
							continue;

						String prop_name = sec.Header.RightProps[i];
#if false
							//I have assumed that two sections shouldn't be setting the same properties...
							if (v.Properties.ContainsKey(prop_name))
								Debug.Print("VPM: property '{0}' was already set", prop_name);
							else
#endif
						v.Add(prop_name, rule.right[i].TypeName);
					}
				}
			}
			return v;
		}

		VpmRule FindApplicableRule(VpmSection sec, TfsSlot ts_var)
		{
			TfsSlot[] vals = new TfsSlot[sec.Header.LeftProps.Length];
			bool[] failed_to_find = new bool[sec.Header.LeftProps.Length];
			for (int i = 0; i < vals.Length; i++)
			{
				if (!sec.Header.LeftProps[i].GetSlot(ts_var, out vals[i]))
					failed_to_find[i] = true;
			}

			foreach (var rule in sec)
			{
				if (rule.op == VpmOperator.Backward)
					continue;

				for (int i = 0; i < vals.Length; i++)
				{
					var r = rule.left[i];
					var kind = r.Kind;

					if (failed_to_find[i] && kind != VpmArgumentKind.DefaultForType)
						goto next_rule;

					switch (kind)
					{
						case VpmArgumentKind.Type:
							if (!mrsm.td[r.TypeName].Subsumes(vals[i].Type))
								goto next_rule;
							break;
						case VpmArgumentKind.DefaultForType:
							if (!mrsm.td[r.TypeName].Subsumes(ts_var.Type()))
								goto next_rule;
							break;
						case VpmArgumentKind.Omit:
							if (!failed_to_find[i])
								goto next_rule;
							break;
						default:
							Debug.Assert(kind == VpmArgumentKind.Default);
							break;
					}
				}
				return rule;
			next_rule: ;
			}

			return null;
		}
	};
}
